package com.opencee.cloud.bpm.controller;

import cn.hutool.core.util.ZipUtil;
import com.google.common.collect.Maps;
import com.opencee.cloud.bpm.entity.BpmCategoryEntity;
import com.opencee.cloud.bpm.mapper.BpmProcessExtendsMapper;
import com.opencee.cloud.bpm.ro.BpmProcessParams;
import com.opencee.cloud.bpm.service.IBpmCategoryService;
import com.opencee.cloud.bpm.service.ProcessEngineService;
import com.opencee.cloud.bpm.vo.BpmProcessDefinitionVO;
import com.opencee.cloud.bpm.vo.BpmProcessInstanceVO;
import com.opencee.cloud.bpm.vo.BpmUserTaskInfoVO;
import com.opencee.common.model.ApiResult;
import com.opencee.common.model.PageResult;
import com.opencee.common.security.SecurityHelper;
import com.opencee.common.utils.WebUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricProcessInstanceQuery;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

/**
 * @author yadu
 */
@Slf4j
@Api(value = "流程管理", tags = "流程管理")
@RestController
@RequestMapping(value = "/process")
public class BpmProcessController {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private SpringProcessEngineConfiguration processEngineConfiguration;
    @Autowired
    private BpmProcessExtendsMapper bpmProcessExtendsMapper;
    @Autowired
    private IBpmCategoryService categoryService;
    @Autowired
    private ProcessEngineService processEngineService;
    @Autowired
    private FormService formService;

    /**
     * 查询流程列表
     *
     * @param params
     * @return
     */
    @ApiOperation(value = "查询流程列表", notes = "查询流程列表")
    @GetMapping(value = "/page")
    public ApiResult<PageResult<BpmProcessDefinitionVO>> page(BpmProcessParams params) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        if (!StringUtils.isEmpty(params.getCategory())) {
            query.processDefinitionCategory(params.getCategory());
        }
        if (!StringUtils.isEmpty(params.getProcessDefinitionKey())) {
            query.processDefinitionKey(params.getProcessDefinitionKey());
        }
        if (!StringUtils.isEmpty(params.getProcessDefinitionName())) {
            query.processDefinitionNameLike(params.getProcessDefinitionName());
        }
        query.latestVersion();
        long count = query.count();
        List<ProcessDefinition> listPage = query.orderByDeploymentId().desc().listPage(params.getStart(), params.getLimit());
        List<String> ids = listPage.stream().map(t -> t.getDeploymentId()).collect(Collectors.toList());
        Set<Long> categoryIds = listPage.stream().filter(t -> !StringUtils.isEmpty(t.getCategory())).map(t -> Long.parseLong(t.getCategory())).collect(Collectors.toSet());
        Map<Long, BpmCategoryEntity> categoryMap = categoryService.mapByIds(categoryIds);
        Map<String, Deployment> deploymentMap = Maps.newHashMap();
        if (!StringUtils.isEmpty(ids)) {
            Map<String, Object> queryMap = Maps.newHashMap();
            queryMap.put("ids", ids);
            List<Deployment> deployment = bpmProcessExtendsMapper.selectDeployment(queryMap);
            deploymentMap = deployment.stream().collect(Collectors.toMap(Deployment::getId, t -> t));
        }
        Map<String, Deployment> finalDeploymentMap = deploymentMap;

        List<BpmProcessDefinitionVO> list = listPage.stream().map(t -> {
            BpmProcessDefinitionVO vo = new BpmProcessDefinitionVO();
            BeanUtils.copyProperties(t, vo);
            Deployment deployment = finalDeploymentMap.get(t.getDeploymentId());
            if (deployment != null) {
                vo.setDeploymentTime(deployment.getDeploymentTime());
            }
            if (!StringUtils.isEmpty(t.getCategory())) {
                Long cid = Long.parseLong(t.getCategory());
                BpmCategoryEntity c = categoryMap.get(cid);
                if (c != null) {
                    vo.setCategoryName(c.getName());
                }
            }
            String startFormKey = formService.getStartFormKey(t.getId());
            vo.setStartFormKey(startFormKey);
            return vo;
        }).collect(Collectors.toList());
        PageResult<BpmProcessDefinitionVO> page = new PageResult();
        page.setRecords(list);
        page.setTotal((int) count);
        return ApiResult.ok().data(page);
    }

    /**
     * 查询最新版流程列表
     *
     * @return
     */
    @ApiOperation(value = "查询最新版流程列表", notes = "查询最新版流程列表")
    @GetMapping("/list/last")
    public ApiResult<List<BpmProcessDefinitionVO>> listLastVersion() {
        List<ProcessDefinition> list = processEngineService.findProcessDefinitionLastVersion();
        Set<Long> categoryIds = list.stream().filter(t -> !org.springframework.util.StringUtils.isEmpty(t.getCategory())).map(t -> Long.parseLong(t.getCategory())).collect(Collectors.toSet());
        Map<Long, BpmCategoryEntity> categoryMap = categoryService.mapByIds(categoryIds);
        List<BpmProcessDefinitionVO> data = list.stream().map(t -> {
            BpmProcessDefinitionVO vo = new BpmProcessDefinitionVO();
            BeanUtils.copyProperties(t, vo);
            if (!org.springframework.util.StringUtils.isEmpty(t.getCategory())) {
                Long cid = Long.parseLong(t.getCategory());
                BpmCategoryEntity c = categoryMap.get(cid);
                if (c != null) {
                    vo.setCategoryName(c.getName());
                }
            }
            String startFormKey = formService.getStartFormKey(t.getId());
            vo.setStartFormKey(startFormKey);
            return vo;
        }).collect(Collectors.toList());
        return ApiResult.ok().data(data);
    }

    /**
     * 删除流程
     *
     * @param deploymentId
     * @return
     */
    @ApiOperation(value = "删除流程", notes = "删除流程")
    @PostMapping(value = "/remove")
    public ApiResult remove(@RequestParam("deploymentId") String deploymentId) {
        repositoryService.deleteDeployment(deploymentId, true);
        return ApiResult.ok();
    }


    /**
     * 下载流程文件
     *
     * @param processDefinitionId 流程实例ID
     * @param response
     * @throws Exception
     */
    @ApiOperation(value = "下载流程文件", notes = "下载流程文件")
    @GetMapping(value = "/download")
    public void downloadFile(@RequestParam("processDefinitionId") String processDefinitionId, HttpServletResponse response)
            throws Exception {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
        String fileName = processDefinition.getKey() + "_" + System.currentTimeMillis() + ".zip";
        WebUtil.setFileDownloadHeader(response, fileName);
        String xml = processDefinition.getResourceName();
        String png = processDefinition.getDiagramResourceName();
        InputStream xmlIn = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), xml);
        InputStream pngIn = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), png);
        OutputStream os = response.getOutputStream();
        ZipUtil.zip(os, new String[]{xml, png}, new InputStream[]{xmlIn, pngIn});
    }

    /**
     * 导入流程
     * 支持文件格式：*.zip、*.bpmn20.xml、*.bpmn.xml、*.bar
     *
     * @param file
     * @return
     * @throws IOException
     */
    @ApiOperation(value = "导入流程", notes = "导入流程")
    @PostMapping(value = "/import")
    public ApiResult importFile(@RequestParam(value = "file") MultipartFile file, @RequestParam(value = "categoryId", defaultValue = "1") String categoryId) throws IOException {
        String fileName = file.getOriginalFilename();
        InputStream fileInputStream = file.getInputStream();
        Deployment deployment = null;
        String extension = FilenameUtils.getExtension(fileName);
        String name = fileName.substring(0, fileName.lastIndexOf(extension));
        if (extension.equals("zip") || extension.equals("bar")) {
            ZipInputStream zip = new ZipInputStream(fileInputStream);
            /** 该函数将DeploymentBuilder isDuplicateFilterEnabled 属性设置为 true
             *  在部署时会检测已部署的相同文件的最后一条记录，如果内容相同，则不会部署
             * **/
            deployment = repositoryService.createDeployment().enableDuplicateFiltering().name(name).key(name).category(categoryId).addZipInputStream(zip).deploy();
            bpmProcessExtendsMapper.updateProcessDefinitionCategory(deployment.getId(), categoryId);
        } else {
            deployment = repositoryService.createDeployment().enableDuplicateFiltering().name(name).key(name).category(categoryId).addInputStream(fileName, fileInputStream).deploy();
            bpmProcessExtendsMapper.updateProcessDefinitionCategory(deployment.getId(), categoryId);
        }
        return ApiResult.ok();
    }


    /**
     * 修改流程状态
     *
     * @param state
     * @param processDefinitionId
     * @return
     */
    @ApiOperation(value = "修改流程状态", notes = "修改流程状态")
    @PostMapping(value = "/update-state")
    public ApiResult updateState(@RequestParam("state") String state, @RequestParam("processDefinitionId") String processDefinitionId) {
        if ("active".equals(state)) {
            repositoryService.activateProcessDefinitionById(processDefinitionId);
        } else if ("suspend".equals(state)) {
            repositoryService.suspendProcessDefinitionById(processDefinitionId);
        }
        return ApiResult.ok();
    }

    /**
     * 查询历史流程列表
     *
     * @param params
     * @return
     */
    @ApiOperation(value = "查询历史流程列表", notes = "查询历史流程列表")
    @GetMapping(value = "/history/page")
    public ApiResult<PageResult<BpmProcessInstanceVO>> pageHistory(BpmProcessParams params) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
        if (!StringUtils.isEmpty(params.getCategory())) {
            query.processDefinitionCategory(params.getCategory());
        }
        if (!StringUtils.isEmpty(params.getProcessDefinitionKey())) {
            query.processDefinitionKey(params.getProcessDefinitionKey());
        }
        if (!StringUtils.isEmpty(params.getProcessDefinitionName())) {
            query.processInstanceNameLike(params.getProcessDefinitionName());
        }
        if (!StringUtils.isEmpty(params.getProcessInstanceId())) {
            query.processInstanceId(params.getProcessInstanceId());
        }
        if (!StringUtils.isEmpty(params.getBusinessKey())) {
            query.processInstanceBusinessKey(params.getBusinessKey());
        }
        if (!StringUtils.isEmpty(params.getStartUserId())) {
            query.involvedUser(params.getStartUserId());
        }
        if (params.getOnlyMy()) {
            query.involvedUser(SecurityHelper.getUserId().toString());
        }

        long count = query.count();
        List<HistoricProcessInstance> listPage = query.orderByProcessInstanceEndTime().desc().listPage(params.getStart(), params.getLimit());
        Map<String, BpmProcessDefinitionVO> defMap = new HashMap<>(1);
        Set<String> defIds = listPage.stream().filter(t -> !StringUtils.isEmpty(t.getProcessDefinitionId())).map(t -> t.getProcessDefinitionId()).collect(Collectors.toSet());
        if (!defIds.isEmpty()) {
            Map<String, Object> map = new HashMap<>(2);
            map.put("ids", defIds);
            List<ProcessDefinition> definitionList = bpmProcessExtendsMapper.selectProcessDefinition(map);
            Set<Long> categoryIds = definitionList.stream().filter(t -> !StringUtils.isEmpty(t.getCategory())).map(t -> Long.parseLong(t.getCategory())).collect(Collectors.toSet());
            Map<Long, BpmCategoryEntity> categoryMap = categoryService.mapByIds(categoryIds);
            defMap = definitionList.stream().map(t -> {
                BpmProcessDefinitionVO vo = new BpmProcessDefinitionVO();
                BeanUtils.copyProperties(t, vo);
                if (!StringUtils.isEmpty(t.getCategory())) {
                    Long cid = Long.parseLong(t.getCategory());
                    BpmCategoryEntity c = categoryMap.get(cid);
                    if (c != null) {
                        vo.setCategoryName(c.getName());
                    }
                }
                return vo;
            }).collect(Collectors.toMap(BpmProcessDefinitionVO::getId, t -> t));
        }

        Map<String, BpmProcessDefinitionVO> finalDefMap = defMap;
        List<BpmProcessInstanceVO> list = listPage.stream().map(t -> {
            BpmProcessInstanceVO vo = new BpmProcessInstanceVO();
            BeanUtils.copyProperties(t, vo);
            BpmProcessDefinitionVO definition = finalDefMap.get(t.getProcessDefinitionId());
            if (definition != null) {
                vo.setCategoryName(definition.getCategoryName());
            }
            return vo;
        }).collect(Collectors.toList());
        PageResult<BpmProcessInstanceVO> page = new PageResult();
        page.setRecords(list);
        page.setTotal((int) count);
        return ApiResult.ok().data(page);
    }

    /**
     * 删除历史流程实例
     *
     * @param processInstanceId
     * @return
     */
    @ApiOperation(value = "删除历史流程实例", notes = "删除历史流程实例")
    @PostMapping(value = "/history/remove")
    public ApiResult removeHistory(@RequestParam("processInstanceId") String processInstanceId) {
        historyService.deleteHistoricProcessInstance(processInstanceId);
        return ApiResult.ok();
    }


    /**
     * 查询运行中的流程
     *
     * @param params
     * @return
     */
    @ApiOperation(value = "查询运行中的流程", notes = "查询运行中的流程")
    @GetMapping(value = "/running/page")
    public ApiResult<PageResult<BpmProcessInstanceVO>> pageRunning(BpmProcessParams params) {
        ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery();
        if (!StringUtils.isEmpty(params.getCategory())) {
            query.processDefinitionCategory(params.getCategory());
        }
        if (!StringUtils.isEmpty(params.getProcessDefinitionKey())) {
            query.processDefinitionKey(params.getProcessDefinitionKey());
        }
        if (!StringUtils.isEmpty(params.getProcessDefinitionName())) {
            query.processInstanceNameLike(params.getProcessDefinitionName());
        }
        if (!StringUtils.isEmpty(params.getProcessInstanceId())) {
            query.processInstanceId(params.getProcessInstanceId());
        }
        if (!StringUtils.isEmpty(params.getBusinessKey())) {
            query.processInstanceBusinessKey(params.getBusinessKey());
        }
        if (!StringUtils.isEmpty(params.getStartUserId())) {
            query.involvedUser(params.getStartUserId());
        }
        if (params.getOnlyMy()) {
            query.involvedUser(SecurityHelper.getUserId().toString());
        }
        long count = query.count();
        List<ProcessInstance> listPage = query.orderByProcessInstanceId().desc().active().listPage(params.getStart(), params.getLimit());
        Map<String, BpmProcessDefinitionVO> defMap = new HashMap<>(1);
        Set<String> defIds = listPage.stream().filter(t -> !StringUtils.isEmpty(t.getProcessDefinitionId())).map(t -> t.getProcessDefinitionId()).collect(Collectors.toSet());
        if (!defIds.isEmpty()) {
            Map<String, Object> map = new HashMap<>(2);
            map.put("ids", defIds);
            List<ProcessDefinition> definitionList = bpmProcessExtendsMapper.selectProcessDefinition(map);
            Set<Long> categoryIds = definitionList.stream().filter(t -> !StringUtils.isEmpty(t.getCategory())).map(t -> Long.parseLong(t.getCategory())).collect(Collectors.toSet());
            Map<Long, BpmCategoryEntity> categoryMap = categoryService.mapByIds(categoryIds);
            defMap = definitionList.stream().map(t -> {
                BpmProcessDefinitionVO vo = new BpmProcessDefinitionVO();
                BeanUtils.copyProperties(t, vo);
                if (!StringUtils.isEmpty(t.getCategory())) {
                    Long cid = Long.parseLong(t.getCategory());
                    BpmCategoryEntity c = categoryMap.get(cid);
                    if (c != null) {
                        vo.setCategoryName(c.getName());
                    }
                }
                return vo;
            }).collect(Collectors.toMap(BpmProcessDefinitionVO::getId, t -> t));
        }
        Map<String, BpmProcessDefinitionVO> finalDefMap = defMap;
        List<BpmProcessInstanceVO> list = listPage.stream().map(t -> {
            BpmProcessInstanceVO vo = new BpmProcessInstanceVO();
            BeanUtils.copyProperties(t, vo);
            BpmProcessDefinitionVO definition = finalDefMap.get(t.getProcessDefinitionId());
            if (definition != null) {
                vo.setCategoryName(definition.getCategoryName());
            }
            return vo;
        }).collect(Collectors.toList());
        PageResult<BpmProcessInstanceVO> page = new PageResult();
        page.setRecords(list);
        page.setTotal((int) count);
        return ApiResult.ok().data(page);
    }


    /**
     * 修改流程实例状态
     */
    @ApiOperation(value = "修改流程实例状态", notes = "修改流程实例状态")
    @PostMapping(value = "/running/update-state")
    public ApiResult updateRunningState(@RequestParam("state") String state, @RequestParam("processInstanceId") String processInstanceId) {
        if ("active".equals(state)) {
            runtimeService.activateProcessInstanceById(processInstanceId);
        } else if ("suspend".equals(state)) {
            runtimeService.suspendProcessInstanceById(processInstanceId);
        }
        return ApiResult.ok();
    }

    /**
     * 删除流程实例
     *
     * @param processInstanceId
     * @return
     */
    @ApiOperation(value = "删除流程实例", notes = "删除流程实例")
    @PostMapping(value = "/running/remove")
    public ApiResult removeRunning(@RequestParam("processInstanceId") String processInstanceId) {
        runtimeService.deleteProcessInstance(processInstanceId, "系统后台删除");
        return ApiResult.ok();
    }

    /**
     * 读取带跟踪的图片
     */
    @ApiOperation(value = "读取带跟踪的图片", notes = "读取带跟踪的图片")
    @GetMapping(value = "/trace")
    public void traceProcess(@RequestParam("processInstanceId") String processInstanceId, HttpServletResponse response)
            throws Exception {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);
        ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds, new ArrayList(),
                processEngineConfiguration.getActivityFontName(),
                processEngineConfiguration.getLabelFontName(),
                processEngineConfiguration.getAnnotationFontName(),
                processEngineConfiguration.getClassLoader(),
                1.0
        );
        IOUtils.copy(in, response.getOutputStream());
        response.flushBuffer();
    }

    /**
     * 查询流程人工任务列表
     *
     * @param processKey
     * @param processInstId
     * @return
     */
    @ApiOperation(value = "查询流程人工任务列表", notes = "查询流程人工任务列表")
    @GetMapping(value = "/getUserTaskList")
    public ApiResult<List<BpmUserTaskInfoVO>> getUserTaskList(@RequestParam("processKey") String processKey, @RequestParam(value = "processInstId", required = false) String processInstId) {
        return ApiResult.ok().data(processEngineService.getUserTaskList(processKey));
    }
}
